package rpg.client.player.swing;

import java.awt.BorderLayout;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import rpg.client.GameEngine;
import rpg.client.Instruction;
import rpg.client.model.Actor;
import rpg.client.model.ActorInfo;
import rpg.client.model.ClientMap;
import rpg.client.model.Dialog;
import rpg.client.model.GameEvent;
import rpg.client.model.GameObject;
import rpg.client.model.Position;
import rpg.client.player.swing.game.InstructionsJPanel;
import rpg.client.player.swing.game.MapJPanel;

public class GameJPanel extends JPanel {
	/**
     * 
     */
	private static final long serialVersionUID = 5584706059412579172L;

	private final TopContainer topContainer;
	private final MapJPanel map;
	private final InstructionsJPanel instructions;
	private final GameEngine engine;
	private volatile String playerID = "";

	private final ExecutorService exec = Executors.newSingleThreadExecutor();

	public GameJPanel(final TopContainer top, final GameEngine gameEngine) {
		super(new BorderLayout());
		topContainer = top;
		this.engine = gameEngine;
		map = new MapJPanel(this);
		instructions = new InstructionsJPanel(this);
		add(map, BorderLayout.WEST);
		add(instructions, BorderLayout.EAST);
	}

	public void character() {
		final SendInstruction si = new SendInstruction(engine, this, true,
				null, getPlayerID());
		exec.submit(si);
	}

	public char[][] getGrid() {
		if (playerID.length() == 0) {
			return new char[0][];
		}

		final ActorInfo playerInfo = engine.character(playerID);
		final String mapID = playerInfo.getPosition().getMapID();
		final ClientMap clientMap = engine.getGameMap(mapID);

		final char[][] grid = new char[clientMap.getRows().size()][];
		int y = 0;
		for (final String row : clientMap.getRows()) {
			grid[y] = row.toCharArray();
			y++;
		}

		// Place NPCs
		for (final ActorInfo ai : clientMap.getNpcs()) {
			final Position p = ai.getPosition();
			grid[(int) p.getY()][(int) p.getX()] = ai.getActor().getGender()
					.name().charAt(0);
		}

		// Place Objects
		for (final GameObject go : clientMap.getObjects()) {
			final Position p = go.getPosition();
			grid[(int) p.getY()][(int) p.getX()] = 'O';
		}

		// Place Player
		final int py = (int) playerInfo.getPosition().getY();
		final int px = (int) playerInfo.getPosition().getX();
		grid[py][px] = '*';

		System.out.println("");
		for (final char[] element : grid) {
			System.out.println(new String(element));
		}

		final char[][] grid55 = new char[MapJPanel.TILES_PER_ROW][];
		final int halfway = (MapJPanel.TILES_PER_ROW - 1) / 2;
		final int minRow = Math.max(0, py - halfway);
		final int minCol = Math.max(0, px - halfway);

		for (int i1 = 0; i1 < MapJPanel.TILES_PER_ROW; i1++) {
			grid55[i1] = new char[MapJPanel.TILES_PER_ROW];
			for (int i2 = 0; i2 < MapJPanel.TILES_PER_ROW; i2++) {
				grid55[i1][i2] = ' ';
				if (minRow + i1 < grid.length) {
					if (minCol + i2 < grid[minRow + i1].length) {
						grid55[i1][i2] = grid[minRow + i1][minCol + i2];
					}
				}
			}
		}

		return grid55;
	}

	public String getPlayerID() {
		return playerID;
	}

	void render(final List<GameEvent> events) {
		for (final GameEvent ge : events) {
			final Render r = new Render(this, playerID, map, ge);
			SwingUtilities.invokeLater(r);
		}
	}

	void renderChat(final String storyID, final String chatID) {
		final Dialog dialog = engine.getDialog(storyID, chatID);
		final Actor actor = engine.getActor(dialog.getActorId());
		ChatJDialog.launch(this, dialog, actor);
	}

	public void request(final Instruction i) {
		System.out.println(i);
		if (i != null) {
			final SendInstruction si = new SendInstruction(engine, this, false,
					i, null);
			exec.submit(si);
			final SendInstruction ui = new SendInstruction(engine, this, false,
					null, playerID);
			exec.submit(ui);
		}
	}

	@Override
	public void requestFocus() {
		map.requestFocus();
	}

	public void requestMove(final Instruction.Type instructionType) {
		Instruction i = null;
		switch (instructionType) {
		case MOVE_NORTH:
		case MOVE_SOUTH:
		case MOVE_WEST:
		case MOVE_EAST:
			i = Instruction.moveDirection(playerID, instructionType);
			break;
		case MOVE_FORWARD:
			i = Instruction.forward(playerID);
			break;
		case TURN_LEFT:
			i = Instruction.turnLeft(playerID);
			break;
		case TURN_RIGHT:
			i = Instruction.turnRight(playerID);
			break;
		default:
			break;
		}

		request(i);
	}

	public void setPlayerID(final String id) {
		playerID = id;
	}

	void updateInstruction(final List<Instruction> possible) {
		instructions.updateInstructions(possible);
	}
}

class Render implements Runnable {
	private final GameEvent event;
	private final GameJPanel game;
	private final MapJPanel map;
	private final String playerID;

	Render(final GameJPanel game, final String playerID, final MapJPanel map,
			final GameEvent ge) {
		super();
		this.event = ge;
		this.game = game;
		this.map = map;
		this.playerID = playerID;
	}

	@Override
	public void run() {
		System.out.println(event);

		switch (event.getType()) {
		case RENDER_CHAT:
			game.renderChat(event.getStoryID(), event.getId());
			break;
		case CHAT_INSTRUCTION:
			game.request(Instruction.startChat(playerID, event.getStoryID(),
					event.getId(), "?"));
			break;
		default:
			map.repaint();
			break;
		}

		game.requestFocus();
	}
}

class SendInstruction implements Runnable {
	private final GameEngine engine;
	private final GameJPanel game;
	private final Instruction instruction;
	private final String playerID;
	private final boolean characterFlag;

	SendInstruction(final GameEngine ge, final GameJPanel gjp,
			final boolean character, final Instruction i, final String pid) {
		super();
		engine = ge;
		game = gjp;
		instruction = i;
		playerID = pid;
		characterFlag = character;
	}

	@Override
	public void run() {
		if (characterFlag) {
			final ActorInfo ai = engine.character(playerID);
			CharacterJDialog.launch(game, ai);
			return;
		}

		if (instruction == null) {
			final List<Instruction> possible = engine
					.getPossibleInstructions(playerID);
			game.updateInstruction(possible);
		} else {
			final List<GameEvent> events = engine.instruction(instruction);
			game.render(events);
		}
	}
}
